<?php

/*
 * This file is part of SwiftMailer. (c) 2009 Fabien Potencier <fabien.potencier@gmail.com> For the full copyright and license information, please view the LICENSE file that was distributed with this source code.
 */

/**
 * Stores Messages on the filesystem.
 * 
 * @package Swift
 * @author Fabien Potencier
 * @author Xavier De Cock <xdecock@gmail.com>
 */
class Swift_FileSpool extends Swift_ConfigurableSpool {
	/**
	 * The spool directory
	 */
	private $_path;
	
	/**
	 * File WriteRetry Limit
	 * 
	 * @var int
	 */
	private $_retryLimit = 10;
	
	/**
	 * Create a new FileSpool.
	 * 
	 * @param string $path        	
	 * @throws Swift_IoException
	 */
	public function __construct($path) {
		$this->_path = $path;
		
		if (! file_exists ( $this->_path )) {
			if (! mkdir ( $this->_path, 0777, true )) {
				throw new Swift_IoException ( 'Unable to create Path [' . $this->_path . ']' );
			}
		}
	}
	
	/**
	 * Tests if this Spool mechanism has started.
	 *
	 * @return boolean
	 */
	public function isStarted() {
		return true;
	}
	
	/**
	 * Starts this Spool mechanism.
	 */
	public function start() {
	}
	
	/**
	 * Stops this Spool mechanism.
	 */
	public function stop() {
	}
	
	/**
	 * Allow to manage the enqueuing retry limit.
	 * Default, is ten and allows over 64^20 different fileNames
	 *
	 * @param integer $limit        	
	 */
	public function setRetryLimit($limit) {
		$this->_retryLimit = $limit;
	}
	
	/**
	 * Queues a message.
	 * 
	 * @param Swift_Mime_Message $message
	 *        	The message to store
	 * @return boolean
	 * @throws Swift_IoException
	 */
	public function queueMessage(Swift_Mime_Message $message) {
		$ser = serialize ( $message );
		$fileName = $this->_path . '/' . $this->getRandomString ( 10 );
		for($i = 0; $i < $this->_retryLimit; ++ $i) {
			/* We try an exclusive creation of the file. This is an atomic operation, it avoid locking mechanism */
			$fp = @fopen ( $fileName . '.message', 'x' );
			if (false !== $fp) {
				if (false === fwrite ( $fp, $ser )) {
					return false;
				}
				
				return fclose ( $fp );
			} else {
				/* The file allready exists, we try a longer fileName */
				$fileName .= $this->getRandomString ( 1 );
			}
		}
		
		throw new Swift_IoException ( 'Unable to create a file for enqueuing Message' );
	}
	
	/**
	 * Execute a recovery if for anyreason a process is sending for too long
	 *
	 * @param int $timeout
	 *        	in second Defaults is for very slow smtp responses
	 */
	public function recover($timeout = 900) {
		foreach ( new DirectoryIterator ( $this->_path ) as $file ) {
			$file = $file->getRealPath ();
			
			if (substr ( $file, - 16 ) == '.message.sending') {
				$lockedtime = filectime ( $file );
				if ((time () - $lockedtime) > $timeout) {
					rename ( $file, substr ( $file, 0, - 8 ) );
				}
			}
		}
	}
	
	/**
	 * Sends messages using the given transport instance.
	 *
	 * @param Swift_Transport $transport
	 *        	A transport instance
	 * @param
	 *        	string[] &$failedRecipients An array of failures by-reference
	 *        	
	 * @return int The number of sent emails
	 */
	public function flushQueue(Swift_Transport $transport, &$failedRecipients = null) {
		if (! $transport->isStarted ()) {
			$transport->start ();
		}
		
		$failedRecipients = ( array ) $failedRecipients;
		$count = 0;
		$time = time ();
		foreach ( new DirectoryIterator ( $this->_path ) as $file ) {
			$file = $file->getRealPath ();
			
			if (substr ( $file, - 8 ) != '.message') {
				continue;
			}
			
			/* We try a rename, it's an atomic operation, and avoid locking the file */
			if (rename ( $file, $file . '.sending' )) {
				$message = unserialize ( file_get_contents ( $file . '.sending' ) );
				
				$count += $transport->send ( $message, $failedRecipients );
				
				unlink ( $file . '.sending' );
			} else {
				/* This message has just been catched by another process */
				continue;
			}
			
			if ($this->getMessageLimit () && $count >= $this->getMessageLimit ()) {
				break;
			}
			
			if ($this->getTimeLimit () && (time () - $time) >= $this->getTimeLimit ()) {
				break;
			}
		}
		
		return $count;
	}
	
	/**
	 * Returns a random string needed to generate a fileName for the queue.
	 * 
	 * @param int $count        	
	 */
	protected function getRandomString($count) {
		// This string MUST stay FS safe, avoid special chars
		$base = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-.";
		$ret = '';
		$strlen = strlen ( $base );
		for($i = 0; $i < $count; ++ $i) {
			$ret .= $base [(( int ) rand ( 0, $strlen - 1 ))];
		}
		
		return $ret;
	}
}
